package de.yvert.jingle.ldr;

import de.yvert.jingle.Image2D;
import de.yvert.jingle.ImageType;
import de.yvert.jingle.hdr.HdrImage2D;

/**
 * Contains color samples for a rectangular area - simply put: an image.<br />
 * Uses an array of int for its internal storage.
 * Colors are stored in RGBA order, e.g. data[0] is red, data[1] is green, 
 * data[2] is blue and data[3] is alpha.
 * This corresponds to the OpenGL 1.1 GL_RGBA format.
 * The row order is top-down (the 0th row is stored first).
 * 
 * This class provides a number of data manipulation functions.
 * 
 * @author Ulf Ochsenfahrt
 */
public class LdrImage2D implements Image2D
{

public static byte clamp(float v)
{
	v = v > 1 ? 1 : v;
	v = v < 0 ? 0 : v;
	return (byte) (v*255);
}

protected byte[] data;
protected int width, height;

public LdrImage2D(int width, int height, byte[] data)
{
	if ((width <= 0) || (height <= 0))
		throw new IllegalArgumentException("An image cannot have negative dimensions.");
	if (data.length != 4*width*height)
		throw new IllegalArgumentException("data doesn't have correct length!");
	this.width = width;
	this.height = height;
	this.data = data;
}

public LdrImage2D(int width, int height)
{ this(width, height, new byte[4*width*height]); }

public LdrImage2D(LdrImage2D other)
{
	this(other.width, other.height);
	System.arraycopy(other.data, 0, this.data, 0, other.data.length);
}

public ImageType getType()
{ return ImageType.RGBA_2D_BYTE; }

public int getWidth()
{ return width;  }

public int getHeight()
{ return height; }

public byte[] getData()
{ return data; }

public LdrImage2D flipY()
{
	byte[] temp = new byte[4*width];
	for (int y = 0; y < height/2; y++)
	{
		System.arraycopy(data, 4*width*y, temp, 0, 4*width);
		System.arraycopy(data, 4*width*(height-1-y), data, 4*width*y, 4*width);
		System.arraycopy(temp, 0, data, 4*width*(height-1-y), 4*width);
	}
	return this;
}

public HdrImage2D toHDR()
{
	HdrImage2D result = new HdrImage2D(width, height);
	float[] to = result.getData();
	for (int i = 0; i < data.length; i++)
		to[i] = (data[i] & 0xff)/255.0f;
	return result;
}


private int checkRange(int x, int y)
{
	if ((x < 0) || (x >= width) || (y < 0) || (y >= height))
		throw new IndexOutOfBoundsException("x,y = "+x+","+y);
	return (y*width+x)*4;
}


public int getRed(int x, int y)
{ return data[checkRange(x, y)+0] & 0xff; }

public int getGreen(int x, int y)
{ return data[checkRange(x, y)+1] & 0xff; }

public int getBlue(int x, int y)
{ return data[checkRange(x, y)+2] & 0xff; }

public int getAlpha(int x, int y)
{ return data[checkRange(x, y)+3] & 0xff; }

public void getRGB(int x, int y, float[] rgb)
{
	int offset = checkRange(x, y);
	rgb[0] = (data[offset+0] & 0xff)/255.0f;
	rgb[1] = (data[offset+1] & 0xff)/255.0f;
	rgb[2] = (data[offset+2] & 0xff)/255.0f;
}

public void getRGBA(int x, int y, float[] rgba)
{
	int offset = checkRange(x, y);
	rgba[0] = (data[offset+0] & 0xff)/255.0f;
	rgba[1] = (data[offset+1] & 0xff)/255.0f;
	rgba[2] = (data[offset+2] & 0xff)/255.0f;
	rgba[3] = (data[offset+3] & 0xff)/255.0f;
}

public int getARGBint(int x, int y)
{
	int offset = checkRange(x, y);
	int r = data[offset+0] & 0xff;
	int g = data[offset+1] & 0xff;
	int b = data[offset+2] & 0xff;
	int a = data[offset+3] & 0xff;
	return (a << 24) | (r << 16) | (g << 8) | b;
}


public void setRGB(int x, int y, float[] rgb)
{
	int offset = checkRange(x, y);
	data[offset+0] = clamp(rgb[0]);
	data[offset+1] = clamp(rgb[1]);
	data[offset+2] = clamp(rgb[2]);
	data[offset+3] = (byte) 255;
}

public void setRGBA(int x, int y, float[] rgba)
{
	int offset = checkRange(x, y);
	data[offset+0] = clamp(rgba[0]);
	data[offset+1] = clamp(rgba[1]);
	data[offset+2] = clamp(rgba[2]);
	data[offset+3] = clamp(rgba[3]);
}

public void setRGB(int x, int y, byte r, byte g, byte b)
{
	int offset = checkRange(x, y);
	data[offset+0] = r;
	data[offset+1] = g;
	data[offset+2] = b;
	data[offset+3] = (byte) 255;
}

public void setRGBA(int x, int y, byte r, byte g, byte b, byte a)
{
	int offset = checkRange(x, y);
	data[offset+0] = r;
	data[offset+1] = g;
	data[offset+2] = b;
	data[offset+3] = a;
}

public void setBGRAint(int x, int y, int rgba)
{
	int offset = checkRange(x, y);
	data[offset+2] = (byte) (rgba);
	data[offset+1] = (byte) (rgba >>  8);
	data[offset+0] = (byte) (rgba >> 16);
	data[offset+3] = (byte) (rgba >> 24);
}


public void decodeRGB(byte[] arr, byte alpha)
{
	for (int i = 0; i < arr.length/3; i++)
	{
		data[4*i+0] = arr[3*i+0];
		data[4*i+1] = arr[3*i+1];
		data[4*i+2] = arr[3*i+2];
		data[4*i+3] = alpha;
	}
}

public void decodeRGB(byte[] arr)
{ decodeRGB(arr, (byte) 255); }

public void decodeRGBA(byte[] arr)
{
	for (int i = 0; i < arr.length/3; i++)
	{
		data[4*i+0] = arr[4*i+0];
		data[4*i+1] = arr[4*i+1];
		data[4*i+2] = arr[4*i+2];
		data[4*i+3] = arr[4*i+3];
	}
}

public void decodeBGRA(byte[] arr)
{
	for (int i = 0; i < arr.length/4; i++)
	{
		data[4*i+2] = arr[4*i+0];
		data[4*i+1] = arr[4*i+1];
		data[4*i+0] = arr[4*i+2];
		data[4*i+3] = arr[4*i+3];
	}
}


/** This is the correct method for <code>java.awt.image.BufferedImage</code> conversion. */
public void decodeARGBint(int[] arr)
{
	for (int i = 0; i < arr.length; i++)
	{
		int color = arr[i];
		data[4*i+2] = (byte) (color >>  0);
		data[4*i+1] = (byte) (color >>  8);
		data[4*i+0] = (byte) (color >> 16);
		data[4*i+3] = (byte) (color >> 24);
	}
}

/** This is the correct method for <code>java.awt.image.BufferedImage</code> conversion. */
public void encodeARGBint(int[] arr)
{
	for (int i = 0; i < arr.length; i++)
	{
		int color = 0;
		color |= (data[4*i+2] & 0xff) << 0;
		color |= (data[4*i+1] & 0xff) << 8;
		color |= (data[4*i+0] & 0xff) << 16;
		color |= (data[4*i+3] & 0xff) << 24;
		arr[i] = color;
	}	
}

public void mixRGBA(int x, int y, float[] rgba)
{
	int offset = checkRange(x, y);
	float da = rgba[3];
	float sa = 1-da;
	data[offset+0] = clamp(da*rgba[0]+sa*data[offset+0]/255.0f);
	data[offset+1] = clamp(da*rgba[1]+sa*data[offset+1]/255.0f);
	data[offset+2] = clamp(da*rgba[2]+sa*data[offset+2]/255.0f);
	data[offset+3] = clamp(1-da*(1-data[offset+3]/255.0f));
}

}
